package com.demo.auth.controller;

import com.alibaba.nacos.common.utils.StringUtils;
import com.demo.auth.constant.RedisKey;
import com.demo.auth.constant.ClientId;
import com.demo.auth.dao.domain.AuthCodeUser;
import com.demo.auth.dao.domain.AuthCodeUserInterface;
import com.demo.auth.dao.service.AuthCodeUserInterfaceService;
import com.demo.auth.dao.service.AuthCodeUserService;
import com.demo.auth.entity.qo.AuthCodeQo;
import com.demo.auth.entity.qo.AuthCodeTokenQo;
import com.demo.auth.service.AuthenticationTokenService;
import com.demo.auth.service.AuthorizationCodeService;
import com.demo.auth.service.TokenStoreService;
import com.demo.starter.dto.GeneralResult;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.support.SessionStatus;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.security.Principal;
import java.util.List;
import java.util.Map;

/**
 * 授权码模式
 */
@RestController
@RequestMapping("/oauth")
public class AuthorizationCodeController {

    @Resource
    private AuthorizationCodeService authorizationCodeService;
    @Resource
    private AuthenticationTokenService authenticationTokenService;
    @Resource
    private TokenStoreService tokenStoreService;
    @Resource
    private AuthCodeUserService authCodeUserService;
    @Resource
    private AuthCodeUserInterfaceService authCodeUserInterfaceService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private TokenEndpoint tokenEndpoint;

    /**
     * 获取授权码
     * 这里生成授权码逻辑参考{@link AuthorizationEndpoint#authorize(Map, Map, SessionStatus, Principal)}这个方法，主要逻辑放在service服务上
     * @param qo 请求参数
     * @return 返回授权码
     */
    @GetMapping("/code")
    public GeneralResult<String> code(@Valid AuthCodeQo qo) {
        AuthCodeUser user = authCodeUserService.queryByUserName(qo.getUserName());
        Assert.notNull(user, "user对象不能为空");

        String code = authorizationCodeService.generateCode(qo.getResponseType(), user);
        return GeneralResult.success(code);
    }

    /**
     * 获取token
     * @param qo 请求参数
     * @return 返回token
     * @throws HttpRequestMethodNotSupportedException 异常
     */
    @GetMapping("/access_token")
    public GeneralResult<OAuth2AccessToken> generateToken(AuthCodeTokenQo qo) throws HttpRequestMethodNotSupportedException {
        AuthCodeUser user = authCodeUserService.queryByUserName(qo.getUserName());
        Assert.notNull(user, "user对象不能为空");

        String code = stringRedisTemplate.opsForValue().get(RedisKey.authCodeKey(user.getId().toString()));
        Assert.isTrue(StringUtils.equals(code, qo.getCode()), "授权码不一致");

        Map<String, String> parameters = Map.of("grant_type", "authorization_code",
                "client_id", ClientId.AUTHORIZATION_CODE,
                "client_secret", "12345",
                "code", qo.getCode());

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                authenticationTokenService.initUsernamePasswordAuthenticationToken(ClientId.AUTHORIZATION_CODE);

        OAuth2AccessToken accessToken = tokenEndpoint.postAccessToken(usernamePasswordAuthenticationToken, parameters).getBody();
        List<AuthCodeUserInterface> authCodeUserInterfaces = authCodeUserInterfaceService.queryByUserId(user.getId());
        tokenStoreService.setAuthCodeToken(user.getId().toString(), accessToken);
        tokenStoreService.setAuthUserInterfaces(user.getId().toString(), authCodeUserInterfaces);
        return GeneralResult.success(accessToken);
    }
}
